1

最近发生一件事, Ruby-China Mongodb注入可导致盗用管理员(他人)身份发帖引起了我的兴趣。

具体内容可以移步到链接去看。随便给出对应的pr地址:https://github.com/ruby-china/ruby-china/commit/ff19cc1fb6d9ea8b0109d7f62741f6652009ff3d

于是我搜索了相关资料,以搬运工的身份写下这篇文章。

MongoDB, no SQL injection?

有人的地方就有江湖,有DB的地方就有injection。SQL数据库如此,No SQL数据库亦是如此。考虑到No SQL数据库用的不是SQL,这里使用SQL injection是否有点不太恰当?不过总不能说是No SQL injection吧XD。

言归正传,OWASP上面有一篇文章, https://www.owasp.org/index.php/Testing_for_NoSQL_injection, 讲到MongoDB的注入问题。另外,MongoDB官方文档FAQ中也提到对于SQL injection的防范方式。其他地方提到的资料基本上大同小异。

根据上面两份资料的内容,以及其他在网上找到的内容:

  1. No SQL不代表安全。由于No SQL使用的查询语言很多是过程式语言而非声明式语言,No SQL注入的危害甚至比传统的SQL数据库更大。

  2. 不要草率地通过拼接用户输入的方式来生成查询语句。这个就不需要举例吧,了解MongoDB查询语言的人,应该可以想象到会是什么结果。

  3. 好消息:MongoDB会在driver中将查询字符串编码成BSON格式。类似于,:{这样的奇奇怪怪的符号会导致BSON构造出错。这样cracker在注入时需要花费更多的心思了。

  4. 坏消息:$.不在上述符号之列。

  5. 又一个好消息:Mongoose宣称,为了提高安全性,它会根据Schema来强制转型输入的内容,因此像$xxx这样的变量会被转化成预定义的类型。如果你还是放心不下,参照 http://stackoverflow.com/questions/15917400/how-dangerous-is-a-mongo-query-which-is-fed-directly-from-a-url-query-string 里面的回答,自己写验证函数。

  6. 最后是坏消息。在下列四个函数中,你可以直接传递字符串作为参数。记住它们的样子,见到它们可以高呼“Eval is evil!”

具体是干什么的,请参考对应文档。

注意$where,这个跟MongoDB ORM(姑且这么称呼)中提供的where调用不一样。这个$where可以接受任意查询表达式,并且返回其结果,相当于特殊的eval。其中Mongoose也特别提到了它:

  • ####NOTE:
  • Only use $where when you have a condition that cannot be met using other MongoDB operators like $lt.
  • Be sure to read about all of its caveats before using.

https://github.com/LearnBoost/mongoose/blob/master/lib/query.js

其实,Ruby China事件其中的主要问题跟MongoDB注入无关……

前面说了一大堆,关于MongoDB注入的问题,但是Ruby China事件的主要问题,跟MongoDB无关……(XD请各位看官先吸口气)

好了,主要的问题是:param的值一定是字符串么?

回到Ruby China的pr上来,这个pr主要是新增

token = params[:token] || oauth_token
+
+      # 防 mongodb 注入
+      token = token.to_s

@current_user ||= User.where(private_token: token).first

显然,token的值不一定是字符串!那么,这个引发了血案的token原本是什么值呢?

我特意用Rails测试了一下:
(注意,因为Rails是通过Rack来解析HTTP参数的,而基本上Ruby Web框架也都是使用Rack,所以下面的结果应该适用于其他Ruby框架)

...
p params[:page]
...

http://localhost:3000/page[$gt]=1
输出{"$gt"=>"1"}

现在大家知道传给User.where的token到底是什么值吧!

继续测试:
http://localhost:3000/page=[1,2]
输出[1,2],嗯...

继续继续:
http://localhost:3000/page=1&page=2
输出2

限于时间和精力,我只测试了Rails的情况。不过根据那篇乌云文章所言,PHP和Node对于HTTP参数的处理方式也跟Rails差不多。

其实,这种攻击方式并不新鲜。它甚至有一个学名,叫做HTTP Parameter Pollution,简称HPP。原理就是利用不同的服务器和服务端框架对HTTP参数的处理方式不同,提交奇奇怪怪的HTTP参数,来绕过逻辑判断。

所以说,搞了半天,最后还是回到一个老问题上了,永远不要相信用户的一切输入!

总结

  1. No SQL也会有SQL injection。
  2. 最好不要拼接查询语句,因为漏洞什么的,你永远也想不到会从哪里冒出来。
  3. 注意MongoDB查询语句中的危险分子。
  4. 永远不要相信用户的输入。无论什么输入,都要在服务端程序中过一下,哪怕是转化成字符串这样的操作也好。

spacewander
5.6k 声望1.5k 粉丝

make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.